
#include <fstream>
#include <ostream>

#include <boost/format.hpp>

#include "inference_engine.hpp"
#include "knowledge_base.hpp"
#include "ilp.hpp"

#include "henry.hpp"

/* The ILP class implementation */
ilp::solver_t::solver_t() {

  try {
    
    m_p_env   = new GRBEnv();
    m_p_model = new GRBModel( *m_p_env );

    /* +1: Minimize, -1: Maximize. */
    m_p_model->set( GRB_IntAttr_ModelSense, 1 );
      
  }
  catch(GRBException e) { cerr << e.getErrorCode() << ":" << e.getMessage() << endl; m_p_env = NULL; }
  
}

ilp::solver_t::~solver_t() {
  
  release();
  
}

void ilp::solver_t::release() {

  if( NULL != m_p_model ) delete m_p_model;
  if( NULL != m_p_env ) delete m_p_env;

  m_p_model = NULL;
  m_p_env   = NULL;
  
}

void ilp::solver_callback_t::update_bounds( double best, double bound, double nodcnt, double runtime ) {
  
  if( -1 == runtime ) runtime = getDoubleInfo(GRB_CB_RUNTIME);
  if( -1 == best ) { if( cerr != *m_p_os ? true : g_verbose_level >= 2 ) *m_p_os << format( "TIME=%.2f BEST=%f BND=%f GAP=%.1f NODES=%.1f" ) % runtime % m_current_objbest % m_current_objbnd % m_current_gap % -1 << endl; return; }
  if( m_current_objbest != best || m_current_objbnd != bound ) {
    double gap = (1.0 - bound/best) * 100.0;
    if( cerr != *m_p_os ? true : g_verbose_level >= 2 ) *m_p_os << format( "TIME=%.2f BEST=%f BND=%f GAP=%.1f NODES=%.1f" ) % runtime % best % bound % gap % nodcnt << endl;
    m_current_objbest = best; m_current_objbnd = bound; m_current_gap = gap;
  }
    
}

void ilp::solver_callback_t::callback() {
  switch( where ) {
  case GRB_CB_MESSAGE: {
    WHISPER( 2, getStringInfo(GRB_CB_MSG_STRING) );
    break; };

  case GRB_CB_MIPNODE: {
    if( GRB_OPTIMAL != getIntInfo( GRB_CB_MIPNODE_STATUS ) ) break;
    update_bounds( getDoubleInfo(GRB_CB_MIPNODE_OBJBST), getDoubleInfo(GRB_CB_MIPNODE_OBJBND), getDoubleInfo(GRB_CB_MIPNODE_NODCNT) );
    if( NULL != m_p_hcb ) m_p_hcb->mipnode( *this );
    break; };

  case GRB_CB_MIPSOL: {
    /* IMPORTANT: THIS IS THE WAY TO GET N-BEST SOLUTIONS. */
    m_has_feasible_solution = true;
    update_bounds( getDoubleInfo(GRB_CB_MIPSOL_OBJBST), getDoubleInfo(GRB_CB_MIPSOL_OBJBND), getDoubleInfo(GRB_CB_MIPSOL_NODCNT) );
    WHISPER( 1, format( "Got new solution! c(H) = %f" ) % getDoubleInfo(GRB_CB_MIPSOL_OBJ) << endl );
    break; };
  }
}

void ilp::solver_t::solve( ostream& log, heuristic_callback_base_t *p_hcb ) {

  solver_callback_t cb( this, m_p_model, &log, p_hcb );
  m_p_model->setCallback( &cb );

  //m_p_model->getEnv().set( GRB_DoubleParam_ObjScale, 0.01 );
//   m_p_model->getEnv().set( GRB_IntParam_RINS, 0 );
//   m_p_model->getEnv().set( GRB_DoubleParam_Heuristics, 0 );
  
  m_p_model->getEnv().set( GRB_IntParam_OutputFlag, 0 );
  GRBEXECUTE( m_p_model->optimize() );
  m_p_model->getEnv().set( GRB_IntParam_OutputFlag, 1 );

  /* Hmm... thanks. Let me see the results... */
  m_has_feasible_solution = cb.has_feasible_solution();
  m_is_solution_optimal   = GRB_OPTIMAL == m_p_model->get( GRB_IntAttr_Status );

  if( m_is_solution_optimal ) cb.update_bounds( get_objfn(), get_objfn(), -1, m_p_model->get( GRB_DoubleAttr_Runtime ) );
  else                        cb.update_bounds( -1, -1, -1, m_p_model->get( GRB_DoubleAttr_Runtime ) );
    
}

void ilp::solver_t::flush() {

  /* Add the stored variables to optimizer. */
  m_num_variables = 0;
  
  foreach( vector<variable_template_t>, iter_vt, m_stored_variables ) {

    if( iter_vt->is_deleted ) continue;
    
    GRBEXECUTE( iter_vt->handle = m_p_model->addVar( iter_vt->lb, iter_vt->ub, iter_vt->coefficient, GRB_BINARY ) );
    m_num_variables++;

  }
  
  GRBEXECUTE( m_p_model->update(); );

  /* Update the constraints. */
  int original_count = 0;
  m_num_constraints = 0; 
  
  foreach( vector<constraint_template_t>, iter_ct, m_stored_constraints ) {

    original_count++;
    
    if( iter_ct->is_deleted ) continue;
    
    constraint_t           cons;
    constraint_template_t &c = (*iter_ct);

    if( iter_ct->is_sos1 ) {

      vector<GRBVar> vars;
      vector<double> coefficients;

      for(int i=0; i<c.coefficients.size(); i++) {
        if( m_stored_variables[ c.variables[i] ].is_deleted )
          { WHISPER( 1, "Fatal: Constraint "<< original_count-1 <<" refers NULL variable " << c.variables[i] << "!" << endl ); }
      
        vars.push_back( m_stored_variables[ c.variables[i] ].handle );
      }

      GRBEXECUTE( m_p_model->addSOS( &vars[0], &c.coefficients[0], c.coefficients.size(), GRB_SOS_TYPE1 ) );
      
    } else {
       
      GRBLinExpr             expr;

      WHISPER( 5, "ilp_solver_t: CON: " );
      
      for(int i=0; i<c.coefficients.size(); i++) {
        if( m_stored_variables[ c.variables[i] ].is_deleted )
          { WHISPER( 1, "Fatal: Constraint "<< original_count-1 <<" refers NULL variable " << c.variables[i] << "!" << endl ); }

        SWHISPER( 5, (i>0 ? " + " : "") << c.coefficients[i] << "*" << m_stored_variables[ c.variables[i] ].name );
        expr += c.coefficients[i] * m_stored_variables[ c.variables[i] ].handle;
      
      }

      if( c.lhs != c.rhs && ilp::CT_NO_MATTER != c.lhs && ilp::CT_NO_MATTER != c.rhs ) {
        SWHISPER( 5, ": " << c.lhs << "~" << c.rhs );
        GRBEXECUTE( m_p_model->addRange( expr, c.lhs, c.rhs ) );
      } else {

        if( c.lhs == c.rhs )                  { GRBEXECUTE( m_p_model->addConstr( expr, GRB_EQUAL, c.rhs ) ); SWHISPER( 5, " = " << c.rhs ); }
        else if( ilp::CT_NO_MATTER == c.rhs ) { GRBEXECUTE( m_p_model->addConstr( expr, GRB_GREATER_EQUAL, c.lhs ) );  SWHISPER( 5, " >= " << c.lhs ); }
        else if( ilp::CT_NO_MATTER == c.lhs ) { GRBEXECUTE( m_p_model->addConstr( expr, GRB_LESS_EQUAL, c.rhs ) ); SWHISPER( 5, " <= " << c.rhs ); }
      
      }

      SWHISPER( 5, endl );
      
    }
    
    m_num_constraints++;
    
  }

  /* Setting the MIP start value. */
  if( m_f_use_mip_start ) {
    foreach( vector<variable_template_t>, iter_vt, m_stored_variables ) {

      if( -1.0 == iter_vt->startv ) continue;

      GRBEXECUTE( iter_vt->handle.set( GRB_DoubleAttr_Start, iter_vt->startv ) );
    
    }
  }
    
}

